其他计算器模块

  计算器的其他模块也可以用类似分析器的方式组织起来。然而,由于这些模块非常小,它们就没必要再有自己的 _impl.h 文件。只有在逻辑模块由许多函数组成,而它们又需要一个共享环境时,才需要使用这种头文件。

  错误处理器已经缩减为一组异常类型,根本不需要error.c:

    error.h:

    namespace Error {
        struct Zero_divide { };

        struct Syntax_error {
            const char* p;
            Syntax_error(const char* q) { p = q; }
        };
    }

词法处理器提供的是一个相当大的而且比较污浊的界面:

    // lexer.h:

    namespace Lexer {
        enum Token_value {
            NAME,            NUMBER,            END,
            PLUS='+',        MINUS='-',         MUL='*',    DIV='\',
            PRINT=';',       ASSIGN='=',        LP='(',     RP=')'
        };

        extern Token_value curr_tok;
        extern double number_value;
        extern std::string string_value;

        Token_value get_token();
    }

除了 lexer.h 外,词法处理器的实现还依赖于 error.h<iostream>,以及在 <cctype> 里声明的那些用于确定字符类别的函数:

    // lexer.c:

    #include "lexer.h"
    #include "error.h"
    #include <iostream>
    #include <cctype>

    Lexer::Token_value Lexer::curr_bok;
    double Lexer::number_value;
    std::string Lexer::string_value;

    Lexer::Token_value Lexer::get_token(){ /* ... */ }

我们也可以将对于 error.h 的#include指令提取出来,放入一个lexer的 _impl.h 文件里。当然,我认为对这种小程序而言,那样做实在太过分了。

  与平常一样,我们将这个模块提供的界面(在这里是lexer.h)用#include包含到模块的实现中,以使编译器能做一致性检查。

  符号表基本上是自足的,因为标准库头文件 <map> 能够带来与之相关的所有东西,那里实现了一个有效的map模板类:

    // table.h:

    #include <map>
    #include <string>

    extern std::map<std::string, double> table;

由于我们假定每个头文件都可能通过#include包含到几个 .c 文件里去,我们必须将table的声明与其定义分开,虽然table.c和table.h的差别仅在一个关键字extern:

    // table.c:

    #include "table.h"

    std::map<std::string, double> table;

简而言之,驱动程序依赖于所有的东西:

    // main.c:

    #include "parser.h"
    #include "lexer.h"
    #include "error.h"
    #include "table.h"

    namespace Driver {
        int no_of_errors;
        std::istream* input;
        void skip();
    }

    #include <sstream>
    int main(int argc, char* argv[]){ /* ... */ }

由于Driver名字空间只是由main()使用,因此我将它放在main.c里。我也可以换另一种方式,将它分出来做成一个driver.h,并用#include包含进来。

  对于更大的系统,通常值得考虑进一步的组织,使驱动程序直接依赖的东西更少一些。也经常值得将main()所做的事情最小化,让main()只是去调用一个位于另一个独立源文件里的驱动函数。对于想作为库去使用的代码,这样做就特别重要。因为此后我们将不能依靠main()里的代码,而必须为来自各种各样函数的调用做好准备(9.6[8])。

🔚